<template>
  <div class="upload-file">
    <el-upload
      multiple
      :action="uploadFileUrl"
      :before-upload="handleBeforeUpload"
      :file-list="fileList"
      :data="data"
      :limit="limit"
      :on-error="handleUploadError"
      :on-exceed="handleExceed"
      :on-success="handleUploadSuccess"
      :on-progress="handleUploadProgress"
      :show-file-list="false"
      :headers="headers"
      class="upload-file-uploader"
      ref="fileUpload"
      v-if="!disabled"
    >
      <!-- 上传按钮 -->
      <el-button size="mini" type="primary">选取文件</el-button>
      <!-- 上传提示 -->
      <div class="el-upload__tip" slot="tip" v-if="showTip">
        请上传
        <template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
        <template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
        的文件
      </div>
    </el-upload>

    <!-- 上传进度显示 -->
    <div v-if="uploadProgress.show" class="upload-progress-container">
      <div class="progress-info">
        <span class="filename">{{ uploadProgress.filename }}</span>
        <span class="progress-text" :class="{'success-text': uploadProgress.status === 'success', 'error-text': uploadProgress.status === 'exception'}">
          {{ getProgressText() }}
        </span>
      </div>
      <el-progress 
        :percentage="uploadProgress.percentage" 
        :status="validProgressStatus"
        :stroke-width="6"
        :show-text="false">
      </el-progress>
    </div>

    <!-- 文件列表 -->
    <transition-group ref="uploadFileList" class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
      <li :key="file.url" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
        <el-link :href="file.url" :underline="false" target="_blank">
          <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
        </el-link>
        <div class="ele-upload-list__item-content-action">
          <el-link :underline="false" @click="handleDelete(index)" type="danger" v-if="!disabled">删除</el-link>
        </div>
      </li>
    </transition-group>
  </div>
</template>

<script>
import { getToken } from "@/utils/auth"
import Sortable from 'sortablejs'
import SparkMD5 from 'spark-md5'
import request from "@/utils/request"

export default {
  name: "FileUpload",
  props: {
    // 值
    value: [String, Object, Array],
    // 上传接口地址
    action: {
      type: String,
      default: "/file/upload"
    },
    // 上传携带的参数
    data: {
      type: Object
    },
    // 数量限制
    limit: {
      type: Number,
      default: 5
    },
    // 大小限制(MB)
    fileSize: {
      type: Number,
      default: 5
    },
    // 文件类型, 例如['png', 'jpg', 'jpeg']
    fileType: {
      type: Array,
      default: () => ["doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "pdf"]
    },
    // 是否显示提示
    isShowTip: {
      type: Boolean,
      default: true
    },
    // 禁用组件（仅查看文件）
    disabled: {
      type: Boolean,
      default: false
    },
    // 拖动排序
    drag: {
      type: Boolean,
      default: true
    },
    // 是否启用分片上传（默认对大于100MB的文件启用）
    enableChunk: {
      type: Boolean,
      default: true
    },
    // 分片大小（MB）
    chunkSize: {
      type: Number,
      default: 50
    },
    // 启用分片上传的文件大小阈值（MB）
    chunkThreshold: {
      type: Number,
      default: 100
    }
  },
  data() {
    return {
      number: 0,
      uploadList: [],
      uploadFileUrl: process.env.VUE_APP_BASE_API + this.action, // 上传文件服务器地址
              headers: {
          Authorization: "Bearer " + getToken(),
        },
        fileList: [],
        // 上传进度相关数据
        uploadProgress: {
          show: false,
          filename: '',
          fileSize: '',
          percentage: 0,
          status: '', // 只能是 'success', 'exception', 'warning', ''
          speed: '0 KB/s',
          loaded: '0 MB',
          total: '0 MB',
          timeRemaining: '',
          startTime: 0,
          lastLoaded: 0,
          lastTime: 0
        },
        // 进度模拟定时器
        progressInterval: null,
        // 分片上传相关数据
        chunkUpload: {
          file: null,
          chunks: [],
          currentChunk: 0,
          totalChunks: 0,
          uploadedChunks: [],
          chunkSize: 0,
          fileName: '',
          fileHash: '',
          isUploading: false,
          uploadId: ''
        }
    }
  },
  mounted() {
    if (this.drag && !this.disabled) {
      this.$nextTick(() => {
        const element = this.$refs.uploadFileList?.$el || this.$refs.uploadFileList
        Sortable.create(element, {
          ghostClass: 'file-upload-darg',
          onEnd: (evt) => {
            const movedItem = this.fileList.splice(evt.oldIndex, 1)[0]
            this.fileList.splice(evt.newIndex, 0, movedItem)
            this.$emit("input", this.listToString(this.fileList))
          }
        })
      })
    }
  },
  watch: {
    value: {
      handler(val) {
        if (val) {
          let temp = 1
          // 首先将值转为数组
          const list = Array.isArray(val) ? val : this.value.split(',')
          // 然后将数组转为对象数组
          this.fileList = list.map(item => {
            if (typeof item === "string") {
              item = { name: item, url: item }
            }
            item.uid = item.uid || new Date().getTime() + temp++
            return item
          })
        } else {
          this.fileList = []
          return []
        }
      },
      deep: true,
      immediate: true
    }
  },
  watch: {
    // 监控进度状态，确保始终为有效值
    'uploadProgress.status'(newStatus) {
      const validStatuses = ['success', 'exception', 'warning', '']
      if (!validStatuses.includes(newStatus)) {
        console.warn('Invalid progress status detected:', newStatus, 'resetting to empty string')
        this.$nextTick(() => {
          this.uploadProgress.status = ''
        })
      }
    }
  },
  computed: {
    // 是否显示提示
    showTip() {
      return this.isShowTip && (this.fileType || this.fileSize)
    },
    // 验证进度条状态值
    validProgressStatus() {
      const validStatuses = ['success', 'exception', 'warning', '']
      const currentStatus = this.uploadProgress.status
      return validStatuses.includes(currentStatus) ? currentStatus : ''
    }
  },
  methods: {
    // 上传前校检格式和大小
    handleBeforeUpload(file) {
      // 校检文件类型
      if (this.fileType) {
        const fileName = file.name.split('.')
        const fileExt = fileName[fileName.length - 1]
        const isTypeOk = this.fileType.indexOf(fileExt) >= 0
        if (!isTypeOk) {
          this.$modal.msgError(`文件格式不正确，请上传${this.fileType.join("/")}格式文件!`)
          return false
        }
      }
      // 校检文件名是否包含特殊字符
      if (file.name.includes(',')) {
        this.$modal.msgError('文件名不正确，不能包含英文逗号!')
        return false
      }
            // 判断是否需要分片上传
      const fileSizeMB = file.size / 1024 / 1024
      const needChunkUpload = this.enableChunk && fileSizeMB > this.chunkThreshold
      
      if (needChunkUpload) {
        // 大文件分片上传
        this.handleChunkUpload(file)
        return false // 阻止默认上传
      } else {
        // 校检文件大小（只对普通上传生效）
        if (this.fileSize) {
          const isLt = fileSizeMB < this.fileSize
          if (!isLt) {
            this.$modal.msgError(`上传文件大小不能超过 ${this.fileSize} MB!`)
            return false
          }
        }
        
        // 初始化上传进度
        this.initUploadProgress(file)
        this.number++
        return true
      }
          },
      
      // 初始化上传进度
      initUploadProgress(file) {
        // 重置为有效的初始状态
        this.uploadProgress = {
          show: true,
          filename: file.name,
          fileSize: this.formatFileSize(file.size),
          percentage: 0,
          status: '', // 确保初始化为有效状态
          speed: '0 KB/s',
          loaded: '0 MB',
          total: this.formatFileSize(file.size),
          timeRemaining: '计算中...',
          startTime: Date.now(),
          lastLoaded: 0,
          lastTime: Date.now()
        }
      },
      
      // 文件个数超出
      handleExceed() {
        this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`)
      },
      
      // 上传进度处理
      handleUploadProgress(event, file, fileList) {
        const currentTime = Date.now()
        const loaded = event.loaded
        const total = event.total
        
        // 进度条分配：
        // 0-70%: 文件传输阶段
        // 70-90%: 服务器接收处理阶段
        // 90-100%: 服务器响应阶段
        const transferPercentage = Math.round((loaded / total) * 70)
        
        // 计算上传速度
        const timeDiff = currentTime - this.uploadProgress.lastTime
        const loadedDiff = loaded - this.uploadProgress.lastLoaded
        
        if (timeDiff > 1000) { // 每秒更新一次速度
          const speed = loadedDiff / (timeDiff / 1000)
          this.uploadProgress.speed = this.formatSpeed(speed)
          
          // 计算剩余时间
          const remainingBytes = total - loaded
          const remainingSeconds = remainingBytes / speed
          this.uploadProgress.timeRemaining = this.formatTime(remainingSeconds)
          
          this.uploadProgress.lastTime = currentTime
          this.uploadProgress.lastLoaded = loaded
        }
        
        this.uploadProgress.percentage = transferPercentage
        this.uploadProgress.loaded = this.formatFileSize(loaded)
        
        // 当传输完成时，进入服务器处理阶段
        if (loaded >= total) {
          this.startServerProcessing()
        }
      },
          // 上传失败
      handleUploadError(err) {
        // 清除进度模拟定时器
        if (this.progressInterval) {
          clearInterval(this.progressInterval)
          this.progressInterval = null
        }
        
        this.completeProgress('exception', '上传失败')
        
        // 延迟隐藏进度条，让用户看到失败状态
        setTimeout(() => {
          this.uploadProgress.show = false
        }, 3000)
        
        this.$modal.msgError("上传文件失败，请重试")
      },
          // 上传成功回调
      handleUploadSuccess(res, file) {
        // 清除进度模拟定时器
        if (this.progressInterval) {
          clearInterval(this.progressInterval)
          this.progressInterval = null
        }
        
        if (res.code === 200) {
          // 快速完成最后10%的进度
          this.completeProgress('success', '上传完成')
          
          this.uploadList.push({ name: res.data.url, url: res.data.url })
          this.uploadedSuccessfully()
          
          // 延迟隐藏进度条，让用户看到成功状态
          setTimeout(() => {
            this.uploadProgress.show = false
          }, 2000)
        } else {
          this.number--
          this.completeProgress('exception', '上传失败')
          
          // 延迟隐藏进度条，让用户看到失败状态
          setTimeout(() => {
            this.uploadProgress.show = false
          }, 3000)
          
          this.$modal.msgError(res.msg)
          this.$refs.fileUpload.handleRemove(file)
          this.uploadedSuccessfully()
        }
      },
    // 删除文件
    handleDelete(index) {
      this.fileList.splice(index, 1)
      this.$emit("input", this.listToString(this.fileList))
    },
    // 上传结束处理
    uploadedSuccessfully() {
              if (this.number > 0 && this.uploadList.length === this.number) {
          this.fileList = this.fileList.concat(this.uploadList)
          this.uploadList = []
          this.number = 0
          this.$emit("input", this.listToString(this.fileList))
        }
    },
    // 获取文件名称
    getFileName(name) {
      // 如果是url那么取最后的名字 如果不是直接返回
      if (name.lastIndexOf("/") > -1) {
        return name.slice(name.lastIndexOf("/") + 1)
      } else {
        return name
      }
    },
    // 对象转成指定字符串分隔
    listToString(list, separator) {
      let strs = ""
      separator = separator || ","
      for (let i in list) {
        strs += list[i].url + separator
              }
        return strs != '' ? strs.substr(0, strs.length - 1) : ''
      },
      
      // 开始服务器处理阶段
      startServerProcessing() {
        this.uploadProgress.loaded = '服务器处理中...'
        this.uploadProgress.percentage = 70
        
        // 模拟服务器处理进度 70% -> 90%
        this.simulateServerProgress()
      },
      
      // 模拟服务器处理进度
      simulateServerProgress() {
        const startPercentage = 70
        const endPercentage = 90
        const duration = 2000 // 2秒内从70%到90%
        const steps = 20 // 分20步完成
        const stepDuration = duration / steps
        const stepIncrement = (endPercentage - startPercentage) / steps
        
        let currentStep = 0
        
        const progressInterval = setInterval(() => {
          currentStep++
          const newPercentage = Math.min(startPercentage + (stepIncrement * currentStep), endPercentage)
          this.uploadProgress.percentage = Math.round(newPercentage)
          
          if (currentStep >= steps) {
            clearInterval(progressInterval)
            // 进入最后阶段 90% -> 等待服务器响应
            this.uploadProgress.percentage = 90
            this.uploadProgress.loaded = '等待服务器响应...'
          }
        }, stepDuration)
        
        // 保存定时器引用，以便在成功/失败时清除
        this.progressInterval = progressInterval
      },
      
      // 完成进度条 (90% -> 100%)
      completeProgress(status, message) {
        // 验证状态值
        const validStatuses = ['success', 'exception', 'warning', '']
        const safeStatus = validStatuses.includes(status) ? status : ''
        
        const startPercentage = Math.max(this.uploadProgress.percentage, 90)
        const endPercentage = 100
        const duration = 500 // 0.5秒内完成
        const steps = 10
        const stepDuration = duration / steps
        const stepIncrement = (endPercentage - startPercentage) / steps
        
        let currentStep = 0
        
        const completeInterval = setInterval(() => {
          currentStep++
          const newPercentage = Math.min(startPercentage + (stepIncrement * currentStep), endPercentage)
          this.uploadProgress.percentage = Math.round(newPercentage)
          
          if (currentStep >= steps) {
            clearInterval(completeInterval)
            this.uploadProgress.percentage = 100
            this.uploadProgress.status = safeStatus
            this.uploadProgress.loaded = message
          }
        }, stepDuration)
      },
      
      // 获取进度显示文本
      getProgressText() {
        if (this.uploadProgress.status === 'success') {
          return '上传完成'
        } else if (this.uploadProgress.status === 'exception') {
          return '上传失败'
        } else if (this.chunkUpload.isUploading) {
          // 分片上传状态
          return this.uploadProgress.loaded
        } else if (this.uploadProgress.loaded === '服务器处理中...') {
          return '服务器处理中...'
        } else if (this.uploadProgress.loaded === '等待服务器响应...') {
          return '等待服务器响应...'
        } else if (typeof this.uploadProgress.loaded === 'string' && this.uploadProgress.loaded.includes('/')) {
          return this.uploadProgress.loaded
        } else {
          return `${this.uploadProgress.loaded} / ${this.uploadProgress.total}`
        }
      },
      
      // 分片上传处理
      async handleChunkUpload(file) {
        try {
          this.initUploadProgress(file)
          this.chunkUpload.isUploading = true
          this.chunkUpload.file = file
          this.chunkUpload.fileName = file.name
          this.uploadProgress.loaded = '计算文件hash...'
          
          // 计算文件hash
          const fileHash = await this.calculateFileHash(file)
          this.chunkUpload.fileHash = fileHash
          
          // 检查文件是否已经上传过（秒传功能）
          const checkResult = await this.checkFileExists(fileHash, file.name)
          if (checkResult && checkResult.exists) {
            this.handleUploadSuccess({ code: 200, data: checkResult.data }, file)
            return
          }
          
          // 创建分片
          this.createChunks(file)
          
          // 检查已上传的分片
          const uploadedChunks = await this.getUploadedChunks(fileHash)
          this.chunkUpload.uploadedChunks = uploadedChunks || []
          
          // 上传分片
          await this.uploadChunks()
          
          // 合并分片
          await this.mergeChunks()
          
        } catch (error) {
          console.error('分片上传失败:', error)
          this.completeProgress('exception', '上传失败')
          this.$modal.msgError('文件上传失败: ' + error.message)
        } finally {
          this.chunkUpload.isUploading = false
        }
      },
      
      // 计算文件hash
      calculateFileHash(file) {
        return new Promise((resolve, reject) => {
          const chunkSize = 2 * 1024 * 1024 // 2MB用于计算hash
          const chunks = Math.ceil(file.size / chunkSize)
          let currentChunk = 0
          const spark = new SparkMD5.ArrayBuffer()
          const fileReader = new FileReader()
          
          fileReader.onload = (e) => {
            spark.append(e.target.result)
            currentChunk++
            
            // 更新hash计算进度 (0-20%)
            const hashProgress = Math.round((currentChunk / chunks) * 20)
            this.uploadProgress.percentage = hashProgress
            
            if (currentChunk < chunks) {
              loadNext()
            } else {
              resolve(spark.end())
            }
          }
          
          fileReader.onerror = () => {
            reject(new Error('文件读取失败'))
          }
          
          const loadNext = () => {
            const start = currentChunk * chunkSize
            const end = Math.min(start + chunkSize, file.size)
            fileReader.readAsArrayBuffer(file.slice(start, end))
          }
          
          loadNext()
        })
      },
      
      // 创建分片
      createChunks(file) {
        const chunkSize = this.chunkSize * 1024 * 1024 // 转换为字节
        const totalChunks = Math.ceil(file.size / chunkSize)
        const chunks = []
        
        for (let i = 0; i < totalChunks; i++) {
          const start = i * chunkSize
          const end = Math.min(start + chunkSize, file.size)
          chunks.push({
            index: i,
            start: start,
            end: end,
            blob: file.slice(start, end),
            size: end - start
          })
        }
        
        this.chunkUpload.chunks = chunks
        this.chunkUpload.totalChunks = totalChunks
        this.chunkUpload.chunkSize = chunkSize
      },
      
      // 检查文件是否存在（秒传）
      async checkFileExists(fileHash, fileName) {
        try {
          const response = await request.post(this.uploadFileUrl + '/check', {
            hash: fileHash,
            fileName: fileName
          })
          return response
        } catch (error) {
          return { exists: false }
        }
      },
      
      // 获取已上传的分片
      async getUploadedChunks(fileHash) {
        try {
          const response = await request.get(this.uploadFileUrl + '/chunks/' + fileHash)
          return response.data || []
        } catch (error) {
          return []
        }
      },
      
      // 上传分片
      async uploadChunks() {
        const { chunks, uploadedChunks } = this.chunkUpload
        const needUploadChunks = chunks.filter(chunk => 
          !uploadedChunks.includes(chunk.index)
        )
        
        this.uploadProgress.loaded = `上传分片 0/${needUploadChunks.length}`
        
        // 并发上传分片（最多3个并发，避免超时）
        const concurrency = 3
        let completedChunks = 0
        
        for (let i = 0; i < needUploadChunks.length; i += concurrency) {
          const batch = needUploadChunks.slice(i, i + concurrency)
          const batchPromises = batch.map(chunk => this.uploadSingleChunk(chunk))
          
          try {
            const batchResults = await Promise.all(batchPromises)
            const successfulChunks = batchResults.filter(r => r.success)
            completedChunks += successfulChunks.length
            
            // 更新进度 (20%-90%，70%用于分片上传)
            const chunkProgress = 20 + Math.round((completedChunks / needUploadChunks.length) * 70)
            this.uploadProgress.percentage = Math.min(chunkProgress, 90)
            this.uploadProgress.loaded = `上传分片 ${completedChunks}/${needUploadChunks.length}`
            
            const failedChunks = batchResults.filter(r => !r.success)
            if (failedChunks.length > 0) {
              throw new Error(`${failedChunks.length}个分片上传失败`)
            }
          } catch (error) {
            throw new Error(`分片上传失败: ${error.message}`)
          }
        }
      },
      
      // 上传单个分片
      async uploadSingleChunk(chunk) {
        try {
          const formData = new FormData()
          formData.append('file', chunk.blob)
          formData.append('hash', this.chunkUpload.fileHash)
          formData.append('index', chunk.index)
          formData.append('total', this.chunkUpload.totalChunks)
          formData.append('fileName', this.chunkUpload.fileName)
          
          const response = await request.post(this.uploadFileUrl + '/chunk', formData, {
            headers: {
              'Content-Type': 'multipart/form-data',
              ...this.headers
            },
            timeout: 300000 // 单个分片5分钟超时
          })
          
          return { success: true, index: chunk.index }
        } catch (error) {
          console.error(`分片${chunk.index}上传失败:`, error)
          return { success: false, index: chunk.index, error }
        }
      },
      
      // 合并分片
      async mergeChunks() {
        this.uploadProgress.percentage = 90
        this.uploadProgress.loaded = '合并文件中...'
        
        const response = await request.post(this.uploadFileUrl + '/merge', {
          hash: this.chunkUpload.fileHash,
          fileName: this.chunkUpload.fileName,
          total: this.chunkUpload.totalChunks,
          size: this.chunkUpload.file.size
        }, {
          headers: this.headers,
          timeout: 600000 // 合并阶段10分钟超时
        })
        
        if (response.code === 200) {
          this.completeProgress('success', '上传完成')
          this.uploadList.push({ name: response.data.url, url: response.data.url })
          this.uploadedSuccessfully()
          
          // 延迟隐藏进度条，让用户看到成功状态
          setTimeout(() => {
            this.uploadProgress.show = false
          }, 2000)
        } else {
          throw new Error(response.msg || '合并分片失败')
        }
      },
      
      // 格式化文件大小
      formatFileSize(bytes) {
        if (bytes === 0) return '0 B'
        const k = 1024
        const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
        const i = Math.floor(Math.log(bytes) / Math.log(k))
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
      },
      
      // 格式化上传速度
      formatSpeed(bytesPerSecond) {
        return this.formatFileSize(bytesPerSecond) + '/s'
      },
      
      // 格式化时间
      formatTime(seconds) {
        if (!isFinite(seconds) || seconds < 0) return '计算中...'
        if (seconds < 60) return Math.round(seconds) + '秒'
        if (seconds < 3600) return Math.round(seconds / 60) + '分钟'
        return Math.round(seconds / 3600) + '小时'
      }
  }
}
</script>

<style scoped lang="scss">
.file-upload-darg {
  opacity: 0.5;
  background: #c8ebfb;
}
.upload-file-uploader {
  margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
  border: 1px solid #e4e7ed;
  line-height: 2;
  margin-bottom: 10px;
  position: relative;
}
.upload-file-list .ele-upload-list__item-content {
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: inherit;
}
.ele-upload-list__item-content-action .el-link {
  margin-right: 10px;
}

/* 上传进度样式 */
.upload-progress-container {
  margin-top: 10px;
  padding: 12px;
  background: #f8f9fa;
  border: 1px solid #e9ecef;
  border-radius: 6px;
}

.progress-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
  font-size: 14px;
}

.filename {
  color: #303133;
  font-weight: 500;
  max-width: 60%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.progress-text {
  color: #909399;
  font-size: 12px;
  transition: color 0.3s ease;
}

.progress-text.success-text {
  color: #67c23a;
  font-weight: 500;
}

.progress-text.error-text {
  color: #f56c6c;
  font-weight: 500;
}
</style>